

namespace Nutils
{
    namespace Shelf
    {
        public class VersionMgr
        {
            // given a list of files, update each to latest file version.
            static public void Convert(string[] files)
            {
                foreach (string file in files)
                {
                    byte fileVersion = GetVersion(file);
                    switch (fileVersion)
                    {
                        case 0:
                            _Convert_0_to_1(file);
                            break;
                        case 1:
                            break;
                        default:
                            Nutils.Console.Error( string.Format("Attempted to convert a file with an unknown version of {0}.", fileVersion) );
                            break;
                    }
                }
            }

            static public byte GetVersion(string filepath)
            {
                // Version 0 was pre-bundle object. It instead used a pure xml file.
                // We can detect this by opening it as an xml and not getting an error.
                try
                {
                    using (System.Xml.XmlTextReader reader = new System.Xml.XmlTextReader(filepath))
                    {
                        XML.Tree xmlTree = XML.Parse(reader);
                        if (null != xmlTree)
                        {
                            return 0;
                        }
                    }
                }
                catch (System.Xml.XmlException)
                {
                }

                // Need the version? Check the bundle's version.
                Nutils.Bundle bundle = new Nutils.Bundle();
                bundle.Unpack(filepath);
                return bundle.Version;
            }
            
            static private void _Convert_0_to_1(string file)
            {
                Nutils.Bundle bundle = null;
                using (System.Xml.XmlTextReader reader = new System.Xml.XmlTextReader(file))
                {
                    XML.Tree xmlTree = XML.Parse(reader);
                    bundle = new Bundle();

                    foreach (XML.Tree node in xmlTree.ForEach("ShelfFile"))
                    {
                        Nutils.Shelf.File shelfFile = new Nutils.Shelf.File(node);
                        bundle.AddString(shelfFile.ID, shelfFile.sDocBody);
                    }
                }
                bundle.AddString("..index.xml...", System.IO.File.ReadAllText(file));
                bundle.Pack(file);
            }
        }

        public class File
        {
            public string sFileNameInDepo;
            public string sRev;
            public string sAction;
            public string sDepoPort;
            public string sDocBody;
            public string ID
            {
                get
                {
                    string id = sDepoPort + ".." + sFileNameInDepo;
                    return id.Replace('/', '|');
                }
            }

            public File(XML.Tree xmlTree)
            {
                sFileNameInDepo = "";
                sRev = "";
                sAction = "";
                sDepoPort = "";
                sDocBody = "";
                _Parse(xmlTree);
            }

            public File(Nutils.P4.CLFile cFile)
            {
                sFileNameInDepo = cFile.DepoFile;
                sRev = cFile.HaveRev;
                sAction = cFile.Action;
                sDepoPort = cFile.DepoHost;
                sDocBody = "";
            }

            public void Inflate(Nutils.Bundle bundle, string destPath)
            {
                if (destPath.Contains("aitest.schema") )
                {
                    int i = 0;
                }

                if( destPath.Contains("\\") )
                {
                    int index = destPath.LastIndexOf("\\");
                    string sDir = destPath.Substring(0, index);

                    if (false == System.IO.Directory.Exists(sDir))
                    {
                        System.IO.Directory.CreateDirectory(sDir);
                    }
                }
                else if( destPath.Contains("/") )
                {
                    int index = destPath.LastIndexOf("/");
                    string sDir = destPath.Substring(0, index);

                    if (false == System.IO.Directory.Exists(sDir))
                    {
                        System.IO.Directory.CreateDirectory(sDir);
                    }
                }

                // 

                using (System.IO.FileStream fileStream = new System.IO.FileStream(destPath, System.IO.FileMode.Create))
                {
                    using (System.IO.BinaryWriter binStream = new System.IO.BinaryWriter(fileStream))
                    {
                        bundle.Inflate(ID, binStream);
                    }
                }
            }

            public void Pack(System.Xml.XmlWriter cWriter)
            {
                cWriter.WriteElementString("FileNameInDepo", sFileNameInDepo);
                cWriter.WriteElementString("RevisionNumber", sRev);
                cWriter.WriteElementString("Action", sAction);
                cWriter.WriteElementString("DepoPort", sDepoPort);
                cWriter.WriteElementString("DocBody", sDocBody);
            }

            private void _Parse(XML.Tree xmlTree)
            {
                sFileNameInDepo = xmlTree.FindTextValue("FileNameInDepo", sFileNameInDepo);
                sRev = xmlTree.FindTextValue("RevisionNumber", sRev);
                sAction = xmlTree.FindTextValue("Action", sAction);
                sDepoPort = xmlTree.FindTextValue("DepoPort", sDepoPort);
                sDocBody = xmlTree.FindTextValue("DocBody", sDocBody);
            }
        }

        public class FileList : System.Collections.Generic.List<File> { };

        public class IShelf
        {
            private static string _shelfdir = @"C:\P4Shelf\";
            public static string sShelfRootDir
            {
                get
                {
                    if (!System.IO.Directory.Exists(_shelfdir))
                    {
                        System.IO.Directory.CreateDirectory(_shelfdir);
                    }
                    return _shelfdir;
                }

                set
                {
                    _shelfdir = value;
                }
            }

            public readonly string sName;
            public readonly string sPackFile;
            public string sJobName;
            public string sComment;
            public string sLastUpdated;
            public string sUpdatedBy;
            public Shelf.FileList lFiles;

            public IShelf(string sName)
            {
                this.sName = sName;
                sPackFile = sShelfRootDir + sName + ".shelf";
                lFiles = new FileList();
            }

            public static string[] FindShelves()
            {
                string[] v = System.IO.Directory.GetFiles(sShelfRootDir, "*.shelf");
                Nutils.Shelf.VersionMgr.Convert(v);
                
                for (int i = 0; i < v.Length; ++i)
                {
                    v[i] = v[i].Replace(sShelfRootDir, "");
                    v[i] = v[i].Replace(".shelf", "");
                }

                return v;
            }

            protected void _Pack(Nutils.P4.MultiDepo multiDepo)
            {
                // Construct the xml index file.
                System.IO.StringWriter indexStream = new System.IO.StringWriter();
                using (System.Xml.XmlWriter cWriter = System.Xml.XmlWriter.Create(indexStream))
                {
                    // cWriter.Formatting = System.Xml.Formatting.Indented;
                    // cWriter.Indentation = 4;
                    // cWriter.Namespaces = false;

                    cWriter.WriteStartDocument();
                    cWriter.WriteStartElement("root");

                    cWriter.WriteElementString("JobName", sJobName);
                    cWriter.WriteElementString("Comment", sComment);

                    foreach (File cShelfFile in lFiles)
                    {
                        cWriter.WriteStartElement("ShelfFile");
                        cShelfFile.Pack(cWriter);
                        cWriter.WriteEndElement();
                    }

                    cWriter.WriteEndDocument();
                }
                
                // bundle it all up.

                Nutils.Bundle bundle = new Bundle();

                foreach (File shelfFile in lFiles)
                {
                    Nutils.P4.Depo depo = multiDepo.FindDepo(shelfFile.sDepoPort);
                    string localPath = depo.GetFileLocalPath(shelfFile.sFileNameInDepo);

                    using (System.IO.Stream reader = System.IO.File.OpenRead(localPath))
                    {
                        bundle.Add(shelfFile.ID, reader);
                    }
                }

                bundle.AddString("..index.xml...", indexStream.ToString());
                bundle.Pack( sPackFile );
            }
        }

        public class Unshelver : IShelf
        {
            private Nutils.Bundle _bundle;
            public Unshelver(string sName): base(sName)
            {
                _bundle = null;
            }

            // Reads the list of files from the database for this shelf
            // (or disk if this is a local shelf)
            public void Unpack()
            {
                lFiles = new FileList();

                // open the bundle, retrieve and parse the index file.
                _bundle = new Nutils.Bundle();
                _bundle.Unpack(sPackFile);

                System.IO.StringReader strReader = _bundle.GetString("..index.xml...");
                XML.Tree xmlTree = XML.Parse(strReader);

                // ShelfFiles
                foreach (XML.Tree node in xmlTree.ForEach("ShelfFile"))
                {
                    Nutils.Shelf.File shelfFile = new Nutils.Shelf.File(node);
                    lFiles.Add(shelfFile);
                }

                // Everything else
                sJobName = xmlTree.FindTextValue("JobName", sJobName);
                sComment = xmlTree.FindTextValue("Comment", sComment);
            }

            private bool _PreInflate(Nutils.P4.MultiDepo cMultiDepo)
            {
                //       
                foreach (Shelf.File cShelvedFile in lFiles)
                {
                    Nutils.P4.Depo cDepo = cMultiDepo.FindDepo(cShelvedFile.sDepoPort);
                    if (null == cDepo)
                    {
                        Nutils.Console.Error("Failed to find a depo for: " + cShelvedFile.sFileNameInDepo);
                        return false;
                    }

                    // 
                    string sPath = cDepo.GetFileLocalPath(cShelvedFile.sFileNameInDepo);
                    if (sPath.Length < 1)
                    {
                        Nutils.Console.Error("Failed to find correct mapping for: " + cShelvedFile.sFileNameInDepo);
                        return false;
                    }

                    //
                    if (cDepo.IsFileCheckedOut(cShelvedFile.sFileNameInDepo))
                    {
                        Nutils.Console.Error("File '" + cShelvedFile.sFileNameInDepo + "' is already checked out.");
                        return false;
                    }
                }
                return true;
            }

            public void Inflate(Nutils.P4.MultiDepo cMultiDepo, bool bDoAutoResolve)
            {
                try
                {
                    if (!_PreInflate(cMultiDepo))
                    {
                        Nutils.Console.Error("Failed to inflate");
                        return;
                    }
                }
                catch (System.Exception ex)
                {
                    Nutils.Console.Error("Failed to inflate, '" + ex.Message + "'.");
                    return;
                }

                foreach (Shelf.File cShelvedFile in lFiles)
                {
                    Nutils.P4.Depo cDepo = cMultiDepo.FindDepo(cShelvedFile.sDepoPort);
                    string sClientFile = cDepo.GetFileLocalPath(cShelvedFile.sFileNameInDepo);

                    if (cShelvedFile.sAction.Equals("edit"))
                    {
                        string sPreInflateHaveRev = cDepo.GetCurrentFileRevision(cShelvedFile.sFileNameInDepo);
                        cDepo.SyncFileToRev(cShelvedFile.sFileNameInDepo, cShelvedFile.sRev);
                        cDepo.Checkout(cShelvedFile.sFileNameInDepo);

                        cShelvedFile.Inflate(_bundle, sClientFile);

                        if (bDoAutoResolve && 
                            sPreInflateHaveRev.Length > 0 &&
                            sPreInflateHaveRev != cShelvedFile.sRev)
                        {
                            cDepo.SyncFileToRev(cShelvedFile.sFileNameInDepo, sPreInflateHaveRev);
                            cDepo.AutoResolve(cShelvedFile.sFileNameInDepo);
                        }
                    }

                    else if (cShelvedFile.sAction.Equals("add"))
                    {
                        cShelvedFile.Inflate(_bundle, sClientFile);
                        cDepo.Add(sClientFile);
                    }

                    else if (cShelvedFile.sAction.Equals("delete"))
                    {
                        cDepo.SyncFileToRev(cShelvedFile.sFileNameInDepo, cShelvedFile.sRev); ;
                        cDepo.Delete(sClientFile);
                    }

                    else
                    {
                        throw new Nutils.Exception("Unknown action: " + cShelvedFile.sAction);
                    }
                }
            }

            public void Update(Nutils.P4.MultiDepo cMultiDepo)
            {
                this._Pack(cMultiDepo);
            }

            public void Delete()
            {
                Hack.CheckFileLock(this.sPackFile);
                System.IO.File.Delete(this.sPackFile);
            }
        }

        public class Shelver : IShelf
        {
            private Nutils.P4.CLFileList _CLFileList;

            public Shelver(string sName)
                : base(sName)
            {
                _CLFileList = new Nutils.P4.CLFileList();
            }

            public void AddFile(Nutils.P4.CLFile cFile)
            {
                Nutils.Shelf.File cShelfFile = new Nutils.Shelf.File(cFile);
                this.lFiles.Add(cShelfFile);
                _CLFileList.Add(cFile);
            }

            // Copies files to the database (or disk if its a local shelf).
            public void Pack(Nutils.P4.MultiDepo cMultiDepo)
            {
                this._Pack(cMultiDepo);
            }

            public void Release()
            {
                foreach (Nutils.P4.CLFile cFile in _CLFileList)
                {
                    cFile.Revert();
                }
            }
        }

    }
}